#include <EB0/EB0.h>

namespace EB0
{
namespace States
{
#pragma region Slides
	// Constructor
	Slideshow::Slide::Slide(char* filename, float duration, bool skippable)
	{
		// Keep track of the filename
		mFilename = filename;

		// Null the image and sprite
		mImage = 0;
		mSprite = 0;

		// Duration and skippable
		mDuration = duration;
		mElapsed = 0.0f;
		mSkippable = skippable;
	}

	// Destructor
	Slideshow::Slide::~Slide()
	{
		// Destroy sprite
		if (mSprite)
		{
			delete mSprite;
			mSprite = 0;
		}

		// Unload image
		if (mImage)
			Managers::ImageManager::getSingletonPtr()->destroyImage(mImage);

		// Reset values
		mDuration = 0.0f;
		mElapsed = 0.0f;
		mSkippable = false;
		mFilename.clear();
	}

	// Get sprite
	sf::Sprite* Slideshow::Slide::getSprite() const
	{
		return mSprite;
	}

	// Is skippable?
	bool Slideshow::Slide::isSkippable() const
	{
		return mSkippable;
	}

	// Set elapsed time variable
	void Slideshow::Slide::setElapsed(float elapsed)
	{
		mElapsed = elapsed;
	}

	// Fade-in slide
	bool Slideshow::Slide::fadeIn(float duration, float dt)
	{
		// Load the image and sprite
		if (!mImage)
			mImage = Managers::ImageManager::getSingletonPtr()->createImage((char*)mFilename.c_str());

		if (!mSprite)
			mSprite = new sf::Sprite(*mImage);

		// Increment elapsed time
		mElapsed += dt;

		// Has the duration been exceeded?
		if (mElapsed > duration)
		{
			// Set image transparency to none
			if (mSprite)
				mSprite->SetColor(sf::Color(255, 255, 255, 255));

			// Reset elapsed for next use
			mElapsed = 0.0f;

			// Fade-in has completed
			return false;
		}

		// Set the transparency level
		if (mSprite)
			mSprite->SetColor(sf::Color(255, 255, 255, (sf::Uint8)(255.0f * mElapsed / duration)));

		// Fade-in has not completed
		return true;
	}

	// Fade-out slide
	bool Slideshow::Slide::fadeOut(float duration, float dt)
	{
		// Increment elapsed time
		mElapsed += dt;

		// Has the duration been exceeded?
		if (mElapsed > duration)
		{
			// Set image transparency to full
			if (mSprite)
				mSprite->SetColor(sf::Color(255, 255, 255, 0));

			// Destroy sprite
			if (mSprite)
			{
				delete mSprite;
				mSprite = 0;
			}

			// Unload image
			if (mImage)
				Managers::ImageManager::getSingletonPtr()->destroyImage(mImage);

			// Reset elapsed for next use
			mElapsed = 0.0f;

			// Fade-out has completed
			return false;
		}

		// Set the transparency level
		if (mSprite)
			mSprite->SetColor(sf::Color(255, 255, 255, 255 - (sf::Uint8)(255.0f * mElapsed / duration)));

		// Fade-out has not completed
		return true;
	}

	// Increment the timer, and return true if the duration has been exceeded (and not equal to 0.0f)
	bool Slideshow::Slide::update(float dt)
	{
		// Increment elapsed time
		mElapsed += dt;

		// Has updating finished?
		if (!FLOAT_EQ(mDuration, 0.0f) && mElapsed > mDuration)
		{
			// Reset the elapsed time for next use
			mElapsed = 0.0f;

			// Return false to indicate updating has completed
			return false;
		}

		// Continue updating
		return true;
	}
#pragma endregion

#pragma region Constructor / Destructor
	// Constructor
	Slideshow::Slideshow()
	{
		// Default values
		mCurrentSlide = 0;
		mPendingSlide = UINT_MAX; // set to max to indicate no pending slide

		mSlideshowRenderable = 0;
		mSlideshowLayer = 0;
		mFadeDuration = 0.0f;
		mTitleBGM = 0;
	}

	// Destructor
	Slideshow::~Slideshow()
	{
		// Reset values
		mCurrentSlide = 0;
		mPendingSlide = UINT_MAX; // set to max to indicate no pending slide

		mSlideshowRenderable = 0;
		mSlideshowLayer = 0;
		mFadeDuration = 0.0f;
		mTitleBGM = 0;
	}
#pragma endregion

#pragma region Load / Unload
	// Load
	void Slideshow::loadSlideshow(Window::RenderWindow* window)
	{
		// Set fade duration
		setFadeDuration(1.0f); // 1 second fade-in + fade-out = 2 seconds total
	}

	// Unload
	void Slideshow::unloadSlideshow(Window::RenderWindow* window)
	{
		// Remove all slides
		destroyAllSlides();
	}
#pragma endregion

#pragma region Create / Destroy Scene
	// Create Scene
	void Slideshow::createSlideshowScene(Window::RenderWindow* window)
	{
		// Create layer for the slideshow
		if (window)
		{
			// Destroy previous layer
			if (mSlideshowLayer)
				window->destroyLayer(mSlideshowLayer);

			// Create new layer
			mSlideshowLayer = window->createLayer();
		}

		// Destroy previous slideshow renderable
		if (mSlideshowRenderable)
		{
			if (mSlideshowLayer)
				mSlideshowLayer->removeRenderable(mSlideshowRenderable);

			delete mSlideshowRenderable;
			mSlideshowRenderable = 0;
		}

		// Set slide indices
		mCurrentSlide = 0;
		mPendingSlide = mCurrentSlide; // indicates fading into the current slide

		// Display current slide
		if (mSlideshowLayer && !mSlides.empty() && mCurrentSlide < mSlides.size() && mSlides.at(mCurrentSlide))
		{
			// Create renderable where slides will be shown
			mSlideshowRenderable = new Graphics::Renderable(mSlides.at(mCurrentSlide)->getSprite());
			mSlideshowLayer->attachRenderable(mSlideshowRenderable);
		}
	}

	// Destroy Scene
	void Slideshow::destroySlideshowScene(Window::RenderWindow* window)
	{
		// Reset slides indices
		mCurrentSlide = 0;
		mPendingSlide = UINT_MAX;

		// Destroy slideshow renderable
		if (mSlideshowRenderable)
		{
			if (mSlideshowLayer)
				mSlideshowLayer->removeRenderable(mSlideshowRenderable);

			delete mSlideshowRenderable;
			mSlideshowRenderable = 0;
		}

		// Destroy slideshow layer
		if (window && mSlideshowLayer)
			window->destroyLayer(mSlideshowLayer);
	}
#pragma endregion

#pragma region Update Slideshow
	// Update slideshow
	bool Slideshow::updateSlideshow(Window::RenderWindow* window, float dt)
	{
		// Is the slideshow still going?
		if (mCurrentSlide < mSlides.size())
		{
			// Are we fading or waiting?
			if (mPendingSlide != UINT_MAX)
			{
				// Are we fading into the current slide or out?
				if (mPendingSlide == mCurrentSlide)
				{
					// We are fading in
					if (!mSlides.at(mCurrentSlide)->fadeIn(mFadeDuration, dt)) // returns false when fade-in has finished
						mPendingSlide = UINT_MAX; // Notify that fading-in has finished
				}
				else // if (mPendingSlide != mCurrentSlide)
				{
					// We are fading out
					if (!mSlides.at(mCurrentSlide)->fadeOut(mFadeDuration, dt)) // returns false when fade-out has finished
					{
						// Show next slide
						mCurrentSlide = mPendingSlide;

						// Make the next slide fully transparent
						// Otherwise we might get a flicker of the image before it's faded in
						if (mCurrentSlide < mSlides.size() && mSlides.at(mCurrentSlide)->getSprite())
							mSlides.at(mCurrentSlide)->getSprite()->SetColor(sf::Color(255, 255, 255, 0));
					}
				}
			}
			else
			{
				// Update the current slide, which will notify if no more slides remain
				// Step forward when the slide has completed
				if (!mSlides.at(mCurrentSlide)->update(dt))
					stepForward();
			}

			// Ensure the proper slide is shown
			if (mCurrentSlide < mSlides.size())
			{
				if (mSlideshowRenderable)
					mSlideshowRenderable->setDrawable(mSlides.at(mCurrentSlide)->getSprite());

				// Continue the slideshow
				return true;
			}
			else
			{
				if (mSlideshowRenderable)
					mSlideshowRenderable->removeDrawable();
			}

			// Otherwise fall through and terminate the slideshow
		}

		// No more slides remain
		return false;
	}

	// Step Forward
	bool Slideshow::stepForward(bool honourSkippable)
	{
		// Jump to the next slide, which will return if the action was possible
		return jumpTo(mCurrentSlide + 1, honourSkippable);
	}

	// Jump to a current slide, return false if out of range
	bool Slideshow::jumpTo(unsigned short index, bool honourSkippable)
	{
		// Cancel if we're in a transition
		if (mPendingSlide != UINT_MAX)
			return true;

		// Set pending slide to the desired index
		// If the index is out of range updateSlideshow() will terminate upon fading out

		// Check if mCurrentSlide is within range
		if (mCurrentSlide < mSlides.size())
		{
			// If honouring skippable, is this slide allowed to continue?
			if (!honourSkippable || mSlides.at(mCurrentSlide)->isSkippable())
				mPendingSlide = index; // notify that a transition will occur

			// Reset elapsed time of current slide
			mSlides.at(mCurrentSlide)->setElapsed(0.0f);
		}

		// Would the new index be out of range?
		return (index < mSlides.size());
	}
#pragma endregion

#pragma region Input and Event Handling
	// On Key Release
	void Slideshow::onKeyRelease(Window::RenderWindow* window, const sf::Event::KeyEvent& key)
	{
		// Handle key release
		switch (key.Code)
		{
			// Step forward in the slideshow
		case sf::Key::Z:
			stepForward(true);
			break;

		default:
			break;
		}
	}
#pragma endregion

#pragma region Slideshow Options
	// Set Fade Duration
	void Slideshow::setFadeDuration(float duration)
	{
		mFadeDuration = duration;
	}

	// Get Fade Duration
	float Slideshow::getFadeDuration() const
	{
		return mFadeDuration;
	}
#pragma endregion

#pragma region Slide Management
	// Add slide to the end
	void Slideshow::createSlide(char* filename, float duration, bool skippable)
	{
		mSlides.push_back(new Slide(filename, duration, skippable));
	}

	// Destroy all slides
	void Slideshow::destroyAllSlides()
	{
		// Destroy all slides
		if (!mSlides.empty())
		{
			SlideVector::iterator slideIter;

			for (slideIter = mSlides.begin(); slideIter != mSlides.end(); slideIter++)
			{
				delete (*slideIter);
				(*slideIter) = 0;
			}

			mSlides.clear();
		}
	}
#pragma endregion
} // namespace States
}